home *** CD-ROM | disk | FTP | other *** search
- /* audio.c -
- **
- ** Sample program that reads TOC from CD and asks CDROM
- ** drive to play contents of audio disc.
- ** Plays only from the first CDROM drive that it finds.
- **
- ** Companion documentation:
- ** - MSCDEX Function Requests
- ** For finding device drivers from MSCDEX
- ** - CDROM Device Driver Specification
- ** For communicating with the physical device
- ** - MSDOS Programmers Reference
- ** Documentation on device drivers and device driver layout
- **
- ** HISTORY:
- ** 10/01/90 Final (v1.0) JohnYG
- */
-
- #include <stdio.h>
- #include <ctype.h>
- #include <dos.h>
- #include <process.h>
-
- /* If WHOLE is defined, play entire disc.
- ** If not defined, play about 10 seconds from each audio track
- */
- #define WHOLE 1
-
- /* Macros for building/taking apart long's */
- #define MAKELONG(lo, hi) ((long)(((unsigned)(lo)) | ((unsigned long)((unsigned)(hi))) << 16))
- #define LOWORD(l) ((ushort)(ulong)(l))
- #define HIWORD(l) ((ushort)(((ulong)(l) >> 16) & 0xffff))
- #define LOBYTE(w) ((uchar)(w))
- #define HIBYTE(w) (((ushort)(w) >> 8) & 0xff)
-
- /* Addressing modes */
- #define ADDR_HSG 0
- #define ADDR_RED 1
-
- /* Device driver commands */
- #define DEVRDIOCTL 3 /* IOCTL read */
- #define DEVPLAY 132 /* Device Play */
- #define DEVSTOP 133 /* Stop device play */
-
- /* CDROM Device IOCTL commands */
- #define IOI_ret_addr 0
- #define IOI_loc_head 1
- #define IOI_io_query 2
- #define IOI_err_stats 3
- #define IOI_audio_info 4
- #define IOI_rd_drv_bytes 5
- #define IOI_dev_status 6
- #define IOI_ret_sectsize 7
- #define IOI_ret_volsize 8
- #define IOI_media_changed 9
- #define IOI_audio_diskinfo 10
- #define IOI_audio_trackinfo 11
- #define IOI_audio_qchaninfo 12
- #define IOI_audio_subinfo 13
- #define IOI_upc_code 14
- #define IOI_cmd_max 14
-
- #define IOO_eject_disc 0
- #define IOO_lock_door 1
- #define IOO_reset_drv 2
- #define IOO_set_audio_param 3
- #define IOO_wr_drv_bytes 4
- #define IOO_cmd_max 4
-
- #define error_drive_not_ready 21
-
- typedef unsigned char uchar;
- typedef unsigned short ushort;
- typedef unsigned int uint;
- typedef unsigned long ulong;
-
- /* Device driver request header */
- typedef struct Request_Hdr {
- uchar rqh_len;
- uchar rqh_unit;
- uchar rqh_cmd;
- ushort rqh_status;
- uchar rqh_rsvd[8];
- } Request_Hdr;
-
- /* Request header for INIT function */
- typedef struct Init_Hdr {
- Request_Hdr init_rqh;
- uchar init_units;
- uchar far *init_endaddr;
- uchar far *init_bpbarr;
- uchar init_devno;
- } Init_Hdr;
-
- /* Request header for IOCTL command */
- typedef struct Ioctl_Hdr {
- Request_Hdr ioctl_rqh;
- uchar ioctl_media;
- uchar far *ioctl_xfer;
- ushort ioctl_nbytes;
- ushort ioctl_sector;
- uchar far *ioctl_volid;
- } Ioctl_Hdr;
-
- /* Request header for Read/Write command */
- typedef struct ReadWriteL_Hdr {
- Request_Hdr rwl_rqh;
- uchar rwl_addrmd;
- uchar far *rwl_xfer;
- ushort rwl_nsects;
- ulong rwl_sectno;
- uchar rwl_mode;
- uchar rwl_ilsize;
- uchar rwl_ilskip;
- ushort rwl_reqno;
- } ReadWriteL_Hdr;
-
- /* Request header for Play Audio command */
- typedef struct PlayReq_Hdr {
- Request_Hdr pl_rqh;
- uchar pl_addrmd;
- ulong pl_start;
- ulong pl_num;
- } PlayReq_Hdr;
-
- /* Record for audio_diskinfo IOCTL call */
- typedef struct DiskInfo_Rec {
- uchar cmd_code;
- uchar lo_tno;
- uchar hi_tno;
- ulong lead_out;
- } DiskInfo_Rec;
-
- /* Record for UPC code IOCTL call */
- typedef struct UPCCode_Rec {
- uchar cmd_code;
- uchar ctrl_adr;
- uchar upc[7];
- uchar zero;
- uchar aframe;
- } UPCCode_Rec;
-
- /* Record for audio_trackinfo IOCTL call */
- typedef struct TnoInfo_Rec {
- uchar cmd_code;
- uchar tno;
- ulong start_addr;
- uchar ctrl;
- } TnoInfo_Rec;
-
- /* Record for audio_qchaninfo IOCTL call */
- typedef struct QchanInfo_Rec {
- uchar cmd_code;
- uchar ctrl;
- uchar tno;
- uchar x;
- uchar min;
- uchar sec;
- uchar frame;
- uchar zero;
- uchar pmin;
- uchar psec;
- uchar pframe;
- } QchanInfo_Rec;
-
- /* Format for CDROM device driver header at CS:0 of device driver
- ** Slightly extended from standard MSDOS character device driver
- */
- typedef struct Dev_Hdr {
- struct Dev_Hdr far *sdevnext; /* Pointer to next dev header */
- ushort sdevatt; /* Attributes of the device */
- void (*sdevstrat)(); /* Strategy entry point */
- void (*sdevint)(); /* Interrupt entry point */
- uchar sdevname[8]; /* Name of device (only first byte used for block) */
- ushort sdevrsvd; /* Reserved word */
- uchar sdevlet; /* Drive letter of first unit */
- uchar sdevunits; /* Number of units handled */
- } Dev_Hdr;
-
- /* Record format for information returned with Get CDROM drive
- ** letter device list function request
- */
- typedef struct Dev_List {
- uchar sub_unit;
- Dev_Hdr far *dev_addr;
- } Dev_List;
-
- /* We use 100 tracks as there are 1-99 tracks on disk and we
- ** use the 100th one to store the location of the lead-out
- ** track.
- */
- TnoInfo_Rec TnoInfo[99 + 1]; /* Table of info for all tracks */
- DiskInfo_Rec DiskInfo; /* Disk information for CD */
- UPCCode_Rec UPCCode; /* UPC code for CD */
- QchanInfo_Rec QInfo; /* Space for returned qchan info*/
- Dev_List Dev_Tbl[26]; /* Table for all device drivers */
- ushort Num_Drives; /* Number of CDROM drives */
- ushort First_DrvLetter; /* First letter used by CDROM's */
- PlayReq_Hdr Play_Rec; /* Record for Play requests */
- Ioctl_Hdr Ioctl_Rec; /* Record for IOCTL requests */
-
- /* External masm routine that sets up request to be passed to
- ** the device driver.
- */
- extern void send_req(ReadWriteL_Hdr far *, Dev_Hdr far *);
-
- /*
- ** Other declarations to make C 6.0 -W2 happy
- */
-
- extern void dsp_addr(unsigned long addr);
- extern void dsp_ctrl(unsigned char c);
- extern unsigned char bcd2bin(unsigned char c);
- extern unsigned long red2hsg(unsigned long l);
- extern unsigned int play(struct Dev_List *drv,unsigned long start,
- unsigned long num, unsigned char mode);
- extern unsigned int ioctl(struct Dev_List *drv,unsigned char *xbuf,
- unsigned char cmd, unsigned char cmdlen);
- extern void read_toc(struct Dev_List *drv);
- extern void play_tracks(struct Dev_List *drv);
- extern void find_drivers(void);
- extern void main(void);
-
- /* dsp_addr() -
- **
- ** DESCRIPTION
- ** Prints red book address in MM:SS:FF Min/Sec/Frame format
- */
- void dsp_addr(addr)
- ulong addr;
- {
- printf("%2d:", HIWORD(addr) & 0xff);
- printf("%02d.", LOWORD(addr) >> 8);
- printf("%02d", LOWORD(addr) & 0xff);
- }
-
- /* dsp_ctrl() -
- **
- ** DESCRIPTION
- ** Prints what attributes are in the 4 bits of track control information.
- */
- void dsp_ctrl(c)
- uchar c;
- {
- printf(" ");
- switch (c & 0xc0) {
- case 0x00:
- case 0x80:
- if (c & 0x80)
- printf("4 chnl ");
- else
- printf("2 chnl ");
- if (c & 0x10)
- printf("w/ pre-emphasis");
- else
- printf("w/o pre-emphasis");
- break;
- case 0x40:
- if (c & 0x10)
- printf("reserved");
- else
- printf("data");
- break;
- case 0xc0:
- printf("reserved");
- break;
- }
- if (c & 0x20)
- printf(" COPY\n");
- else
- printf(" !COPY\n");
- }
-
- /* bcd2bin() -
- **
- ** DESCRIPTION
- ** Converts BCD to binary. BCD breaks a byte into two 4-bit
- ** nibbles where each ranges from 0-9. BCD can represent 0-99
- ** whereas binary does 0-255. If the value passed in is an
- ** illegal BCD value, we return 0xff
- */
- uchar bcd2bin(c)
- uchar c;
- {
- if ((c & 0x0f) > 0x09)
- return(0xff);
- if ((c & 0xf0) > 0x90)
- return(0xff);
- return((((c & 0xf0) >> 4) * 10) + (c & 0x0f));
- }
-
- /* red2hsg() -
- **
- ** DESCRIPTION
- ** Converts a binary red book address to high sierra addressing
- ** The msb of the red book is always zero, the next less significant
- ** byte is the minute (0-59+), then second (0-59) and lsb is the
- ** frame (0-75). The conversion is
- ** hsg = min * 60 * 75 + sec * 75 + frame;
- */
- ulong red2hsg(l)
- ulong l;
- {
- return((ulong) (HIWORD(l) & 0xff) * 60 * 75 +
- (ulong) (LOWORD(l) >> 8) * 75 +
- (ulong) (LOWORD(l) & 0xff));
- }
-
-
- /* play() -
- **
- ** DESCRIPTION
- ** Sends the request to play num frames at address start on
- ** drive drv. Mode determines whether the starting address is to
- ** be interpreted as high sierra or red book addressing.
- ** If num == 0, then instead of sending a PLAY-AUDIO command,
- ** we issue a STOP-AUDIO command.
- */
- uint play(drv, start, num, mode)
- Dev_List *drv;
- ulong start;
- ulong num;
- uchar mode;
- {
- register PlayReq_Hdr *req = &Play_Rec;
-
- printf("Drive %8Fs", drv->dev_addr->sdevname);
- printf(" unit %d", drv->sub_unit);
- if (mode == ADDR_HSG)
- printf(" start %ld num %ld\n", start, num);
- else {
- printf(" start ");
- dsp_addr(start);
- printf(" num %ld\n", num);
- }
-
- req->pl_rqh.rqh_len = sizeof(PlayReq_Hdr);
- req->pl_rqh.rqh_unit = drv->sub_unit;
- req->pl_rqh.rqh_cmd = (uchar) (num ? DEVPLAY : DEVSTOP);
- req->pl_rqh.rqh_status = 0;
-
- req->pl_addrmd = mode;
- req->pl_start = start;
- req->pl_num = num;
-
- send_req((ReadWriteL_Hdr far *) req, drv->dev_addr);
- if (req->pl_rqh.rqh_status & 0x8000) {
- printf(" Error on play - status = 0x%r\n", req->pl_rqh.rqh_status, 0);
- return(error_drive_not_ready);
- }
-
- return(0);
- }
-
- /* ioctl() -
- **
- ** DESCRIPTION
- ** Sends an IOCTL request to the device driver in drv. The
- ** ioctl command is cmd and the ioctl cmd length is cmdlen.
- ** The buffer for the command is pointed to by xbuf.
- */
- uint ioctl(drv, xbuf, cmd, cmdlen)
- Dev_List *drv;
- uchar *xbuf;
- uchar cmd;
- uchar cmdlen;
- {
- register Ioctl_Hdr *io = &Ioctl_Rec;
-
- io->ioctl_rqh.rqh_len = sizeof(Ioctl_Hdr);
- io->ioctl_rqh.rqh_unit = drv->sub_unit;
- io->ioctl_rqh.rqh_cmd = DEVRDIOCTL;
- io->ioctl_rqh.rqh_status = 0;
-
- io->ioctl_media = 0;
- io->ioctl_xfer = (char far *) xbuf;
- *xbuf = cmd;
- io->ioctl_nbytes = cmdlen;
- io->ioctl_sector = 0;
- io->ioctl_volid = 0;
-
- send_req((ReadWriteL_Hdr far *) io, drv->dev_addr);
- if (io->ioctl_rqh.rqh_status & 0x8000) {
- printf(" Error on play - status = 0x%r\n", io->ioctl_rqh.rqh_status);
- return(error_drive_not_ready);
- }
- return(0);
- }
-
- /* read_toc() -
- **
- ** DESCRIPTION
- ** Reads the disk information from the TOC in the qchannel
- ** to find the first and last track numbers and builds a
- ** table (TnoInfo) of the starting address for each track by
- ** asking the device driver for the info for each track.
- */
- void read_toc(drv)
- Dev_List *drv;
- {
- TnoInfo_Rec *t;
- QchanInfo_Rec *q = &QInfo;
- uchar *s;
- uint i;
-
- if (ioctl(drv,(uchar *) &DiskInfo, IOI_audio_diskinfo,
- sizeof(DiskInfo_Rec)))
- printf(" Failed to read TOC\n");
-
- printf("DiskInfo.lo_tno %d ", DiskInfo.lo_tno);
- printf("DiskInfo.hi_tno %d ", DiskInfo.hi_tno);
- printf("DiskInfo.lead_out ");
- dsp_addr(DiskInfo.lead_out);
- printf("\n");
-
- if (ioctl(drv,(uchar *) &UPCCode, IOI_upc_code, sizeof(UPCCode_Rec)))
- printf(" Failed to find UPC code\n");
-
- printf("UPC code: ");
- if (UPCCode.ctrl_adr == 0)
- printf("Failed to find UPC code\n");
- else {
- s = UPCCode.upc;
- for (i = 0; i < 7; i++) {
- printf("%02d.%02d.", (*s & 0xf0) >> 4, *s & 0x0f);
- s++;
- }
- printf(" zero = %d", UPCCode.zero);
- printf(" aframe = %d\n", bcd2bin(UPCCode.aframe));
- }
-
- /* The entry after the last track has as it's
- ** starting address the beginning of the lead-out
- ** track which is the end of the audio on the disc.
- ** This is why we have 99+1 records in TnoInfo
- */
- TnoInfo[DiskInfo.hi_tno + 1].start_addr = DiskInfo.lead_out;
-
- t = &TnoInfo[DiskInfo.lo_tno];
- for (i = DiskInfo.lo_tno; i <= DiskInfo.hi_tno; i++) {
- t->tno = (uchar) i;
- ioctl(drv,(uchar *) t, IOI_audio_trackinfo, sizeof(TnoInfo_Rec));
- printf("tno %2d ", t->tno);
- printf("start_addr ");
- dsp_addr(t->start_addr);
- printf(" ctrl 0x%02x ", t->ctrl);
- dsp_ctrl(t->ctrl);
- t++;
- }
- }
-
- /* play_tracks() -
- **
- ** DESCRIPTION
- ** If WHOLE is defined, then we play the entire disc.
- ** Otherwise we play about 10 seconds (the amount of time
- ** to do 200 qchannel queries) from each track on the
- ** disk
- */
- void play_tracks(drv)
- Dev_List *drv;
- {
- TnoInfo_Rec *t;
- QchanInfo_Rec *q = &QInfo;
- uint i;
- uint j;
- uint k;
- ulong num;
- uchar *s;
-
- t = &TnoInfo[DiskInfo.lo_tno];
-
- #ifdef WHOLE
- num = red2hsg(DiskInfo.lead_out) - red2hsg(t->start_addr);
- play(drv, t->start_addr, num, ADDR_RED);
- #else
- /* Play each track on disc */
- for (i = DiskInfo.lo_tno; i <= DiskInfo.hi_tno; i++) {
- /* Calc number of frames to play */
- num = red2hsg(TnoInfo[i + 1].start_addr) - red2hsg(TnoInfo[i].start_addr);
- /* Begin playing audio for this track */
- play(drv, t->start_addr, num, ADDR_RED);
- t++;
- /* Loop 200 times and report the qchannel status */
- j = 0;
- do {
- ioctl(drv, q, IOI_audio_qchaninfo, sizeof(QchanInfo_Rec));
- printf("ctrl 0x%02x ", q->ctrl);
- if ((q->ctrl & 0x0f) == 3) {
- printf("ISRC code: display Nimp.\n");
- }
- else if ((q->ctrl & 0x0f) == 2) {
- printf("UPC code: ");
- s = &q->tno;
- for (k = 0; k < 8; k++) {
- printf("%02d.%02d.", (*s & 0xf0) >> 4, *s & 0x0f);
- s++;
- }
-
- printf("frame = %2d\n", bcd2bin(q->pframe));
- }
- else {
- printf("tno %2d ", bcd2bin(q->tno));
- printf("x %2d ", bcd2bin(q->x));
- printf("%2d:%02d.%02d", q->min, q->sec, q->frame);
- printf(" zero = 0x%02x", q->zero);
- printf(" %2d:%02d.%02d", q->pmin, q->psec, q->pframe);
- printf("\n");
- }
- #ifdef TRACK_BY_TRACK
- /* NOTE: This is what I'd like to have except that when seeking
- ** to the beginning of the audio track, the qchannel status
- ** has apparently random entries and also as it homes in on the
- ** track, it begins reporting its position before it's actually
- ** hit the frame we want. Sometimes these entries are in the
- ** previous or next track so that this test fails and we move
- ** on before we've even begun to play the audio we asked to
- ** seek to.
- */
- disc_time = (ulong) q->min << 16 +
- (ulong) q->sec << 8 +
- (ulong) q->frame;
- } while (q->tno == 0 || (red2hsg(disc_time) < red2hsg(t->start_addr)));
- #else
- } while (j++ < 100);
- #endif
- /* Stop the audio */
- play(drv, 0L, 0L, ADDR_RED);
- }
- #endif
- }
-
- /* find_drivers() -
- **
- ** Using INT 2Fh with AH=15h (the MSCDEX function request interface)
- ** we can ask MSCDEX for the number and location of all CDROM
- ** device drivers on the system.
- **
- ** Unfortunately at present, if MSCDEX is not present, the carry
- ** flag is not set when an INT 2Fh is issued with AH=15h...I'll
- ** figure out what's going on and figure out a good way to tell
- ** if MSCDEX is present or not.
- */
- void find_drivers()
- {
- union REGS inregs;
- union REGS outregs;
- struct SREGS segregs;
- uchar far *d = (uchar far *) Dev_Tbl;
- Dev_Hdr far *sdev;
- uint i;
-
- inregs.x.ax = 0x1500; /* Get Number of CDROM drive letters */
- inregs.x.bx = 0; /* Init to zero */
- int86(0x2f, &inregs, &outregs);
-
- /* If number of drives returned is still 0, then MSCDEX
- ** is not installed.
- */
- if (outregs.x.bx == 0) {
- printf("MSCDEX not installed\n");
- exit(1);
- }
-
- Num_Drives = outregs.x.bx;
- First_DrvLetter = outregs.x.cx;
- printf("There are %d drives starting at %c:\n", Num_Drives, First_DrvLetter + 'A');
-
- inregs.x.ax = 0x1501; /* Get CDROM drive letter device list */
- inregs.x.bx = LOWORD(d);
- segregs.es = HIWORD(d);
- int86x(0x2f, &inregs, &outregs, &segregs);
-
- /* Check if carry set */
- if (outregs.x.cflag) {
- printf("MSCDEX not present\n");
- exit(1);
- }
-
- for (i = 0; i < Num_Drives; i++) {
- printf("Drive %d unit %d addr 0x%lx ", i, Dev_Tbl[i].sub_unit, Dev_Tbl[i].dev_addr);
- sdev = Dev_Tbl[i].dev_addr;
- printf("'%8Fs'\n", sdev->sdevname);
- }
- }
-
- void main()
- {
- find_drivers();
- read_toc(&Dev_Tbl[0]);
- play_tracks(&Dev_Tbl[0]);
- }
-